﻿using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System.Linq;
using matrix = Microsoft.Xna.Framework.Matrix;

namespace XnaCanvas {
    public class XObj3 : XObject {
        public XObj3(PrimitiveType pt = PrimitiveType.TriangleList) {
            this.vertices = new List<VertexPositionNormalColor>();
            this.indices = new List<ushort>();
            base.PrimitivType = pt;
        }
        public override void Present() {
            effect.World = World;
            effect.View = xsence.viewer;
            effect.Projection = xsence.projection;
            effect.DiffuseColor = xsence.diffuseColor.ToVector3();
            effect.Alpha = xsence.diffuseColor.A / 255.0f;
            XSence.Device.SetVertexBuffer(this.vbuffer);
            XSence.Device.Indices = ibuffer;
            foreach (var pass in effect.CurrentTechnique.Passes) {
                pass.Apply();
                XSence.Device.DrawIndexedPrimitives(PrimitivType, 0, 0, vertices.Count, 0, primitiveCount);
            }
        }
        public void AddIndex3D(int i, int j, int k) {
            indices.Add((ushort)i);
            indices.Add((ushort)j);
            indices.Add((ushort)k);
        }
        #region basic obj
        /// <summary>
        /// 构造 多底面多边体 ，每个底面是之间是相互平等的，且呈中心相似的，物体扫描中心点在 初始绘制底面的圆心上
        /// </summary>
        /// <param name="pattern">基底形态采样函数</param>
        /// <param name="hrf">高半径采样函数</param>
        /// <param name="angles">基底采样0-2pi</param>
        /// <param name="hrs">高度采样</param>
        public void LoadParallelPolyhedronPrimitive(Func<float, float> pattern, Func<float, float> hrf, float[] angles, float[] hrs) {
            ClearBuffer();
            int acount = angles.Length;
            int hrcount = hrs.Length;
            if (angles.Max() > MathHelper.TwoPi || angles.Min() < 0) {
                throw new Exception("值不在预期范围,angles数组只能在0，到2pi之间!");
            }
            //画两个底面
            ushort currentnumber = 0;
            for (int i = 0; i < acount; i++) {
                //var direct = hrs[0] > hrs[hrs.Length - 1] ? 1 : -1;
                var normal = Vector3.Zero;
                var rpi = hrf(hrs[0]);//开始的那个底面
                var r = rpi * pattern(angles[i]);
                var x = (float)(r * Math.Cos(angles[i]));
                var y = (float)(r * Math.Sin(angles[i]));
                var position = new Vector3(x, y, (float)hrs[0]);
                this.vertices.Add(new VertexPositionNormalColor {
                    position = position, normal = normal, color = this.XColor
                });
            }
            for (int i = 0; i < acount - 2; i++) {
                this.indices.Add(currentnumber);
                this.indices.Add((ushort)(i + 1));
                this.indices.Add((ushort)(i + 2));
            }
            currentnumber = (ushort)this.vertices.Count;
            for (int i = 0; i < acount; i++) {
                //var direct = hrs[0] > hrs[hrs.Length - 1] ? 1 : -1;
                //var normal = new Vector3(0, 0, direct);
                var normal = Vector3.Zero;
                var rpi = hrf(hrs[hrs.Length - 1]);//最末那个底面
                var r = rpi * pattern(angles[i]);
                var x = (float)(r * Math.Cos(angles[i]));
                var y = (float)(r * Math.Sin(angles[i]));
                var position = new Vector3(x, y, (float)hrs[hrs.Length - 1]);
                this.vertices.Add(new VertexPositionNormalColor {
                    position = position, normal = normal, color = this.XColor
                });
            }
            for (int i = 0; i < acount - 2; i++) {
                this.indices.Add((ushort)(currentnumber + i + 2));
                this.indices.Add((ushort)(currentnumber + i + 1));
                this.indices.Add(currentnumber);
            }
            //绘制四周的顶点
            for (int i = 0; i < hrcount - 1; i++) {
                for (int j = 0; j < acount; j++) {
                    var nj = (j + 1) % acount;
                    var ni = (i + 1) % hrcount;
                    var rpi = hrf(hrs[i]);
                    var r1 = rpi * pattern(angles[j]);
                    var r2 = rpi * pattern(angles[nj]);
                    var x1 = (float)(r1 * Math.Cos(angles[j]));
                    var x2 = (float)(r2 * Math.Cos(angles[nj]));
                    var y1 = (float)(r1 * Math.Sin(angles[j]));
                    var y2 = (float)(r2 * Math.Sin(angles[nj]));
                    Vector3 v1, v2, v7, v8;
                    v1 = new Vector3(x1, y1, (float)hrs[i]);
                    v2 = new Vector3(x2, y2, (float)hrs[i]);
                    var rpi2 = hrf(hrs[ni]);
                    var r3 = rpi2 * pattern(angles[j]);
                    var r4 = rpi2 * pattern(angles[nj]);
                    var x7 = (float)(r3 * Math.Cos(angles[j]));
                    var x8 = (float)(r4 * Math.Cos(angles[nj]));
                    var y7 = (float)(r3 * Math.Sin(angles[j]));
                    var y8 = (float)(r4 * Math.Sin(angles[nj]));
                    v7 = new Vector3(x7, y7, (float)hrs[ni]);
                    v8 = new Vector3(x8, y8, (float)hrs[ni]);
                    var normal = Vector3.Zero;
                    this.indices.Add((ushort)(this.vertices.Count + 2));
                    this.indices.Add((ushort)(this.vertices.Count + 1));
                    this.indices.Add((ushort)(this.vertices.Count + 0));
                    //
                    this.indices.Add((ushort)(this.vertices.Count + 2));
                    this.indices.Add((ushort)(this.vertices.Count + 3));
                    this.indices.Add((ushort)(this.vertices.Count + 1));
                    //
                    this.vertices.Add(new VertexPositionNormalColor(v1, normal, this.XColor));
                    this.vertices.Add(new VertexPositionNormalColor(v2, normal, this.XColor));
                    this.vertices.Add(new VertexPositionNormalColor(v7, normal, this.XColor));
                    this.vertices.Add(new VertexPositionNormalColor(v8, normal, this.XColor));
                }
            }
            ResetNomal();
            this.SetData();
        }
        public void LoadParallelPolyhedronPrimitive(int biannum = 5, float r = 1, float h = 1) {
            ClearBuffer();
            var angles = new float[biannum];
            for (int i = 0; i < biannum; i++) {
                angles[i] = MathHelper.TwoPi * i / biannum;
            }
            int acount = angles.Length;
            //画两个底面
            ushort currentnumber = 0;
            for (int i = 0; i < acount; i++) {
                var normal = Vector3.Zero;
                var x = (float)(r * Math.Cos(angles[i]));
                var y = (float)(r * Math.Sin(angles[i]));
                var position = new Vector3(x, y, 0);
                this.vertices.Add(new VertexPositionNormalColor {
                    position = position, normal = normal, color = this.XColor
                });
            }
            for (int i = 0; i < acount - 2; i++) {
                this.indices.Add(currentnumber);
                this.indices.Add((ushort)(i + 1));
                this.indices.Add((ushort)(i + 2));
            }
            currentnumber = (ushort)this.vertices.Count;
            for (int i = 0; i < acount; i++) {
                var normal = Vector3.Zero;
                var x = (float)(r * Math.Cos(angles[i]));
                var y = (float)(r * Math.Sin(angles[i]));
                var position = new Vector3(x, y, h);
                this.vertices.Add(new VertexPositionNormalColor {
                    position = position, normal = normal, color = this.XColor
                });
            }
            for (int i = 0; i < acount - 2; i++) {
                this.indices.Add((ushort)(currentnumber + i + 2));
                this.indices.Add((ushort)(currentnumber + i + 1));
                this.indices.Add(currentnumber);
            }
            //绘制四周的顶点
            for (int j = 0; j < acount; j++) {
                var nj = (j + 1) % acount;
                var x1 = (float)(r * Math.Cos(angles[j]));
                var x2 = (float)(r * Math.Cos(angles[nj]));
                var y1 = (float)(r * Math.Sin(angles[j]));
                var y2 = (float)(r * Math.Sin(angles[nj]));
                Vector3 v1, v2, v7, v8;
                v1 = new Vector3(x1, y1, 0);
                v2 = new Vector3(x2, y2, 0);
                var x7 = (float)(r * Math.Cos(angles[j]));
                var x8 = (float)(r * Math.Cos(angles[nj]));
                var y7 = (float)(r * Math.Sin(angles[j]));
                var y8 = (float)(r * Math.Sin(angles[nj]));
                v7 = new Vector3(x7, y7, h);
                v8 = new Vector3(x8, y8, h);
                var normal = Vector3.Zero;
                this.indices.Add((ushort)(this.vertices.Count + 2));
                this.indices.Add((ushort)(this.vertices.Count + 1));
                this.indices.Add((ushort)(this.vertices.Count + 0));
                //
                this.indices.Add((ushort)(this.vertices.Count + 2));
                this.indices.Add((ushort)(this.vertices.Count + 3));
                this.indices.Add((ushort)(this.vertices.Count + 1));
                //
                this.vertices.Add(new VertexPositionNormalColor(v1, normal, this.XColor));
                this.vertices.Add(new VertexPositionNormalColor(v2, normal, this.XColor));
                this.vertices.Add(new VertexPositionNormalColor(v7, normal, this.XColor));
                this.vertices.Add(new VertexPositionNormalColor(v8, normal, this.XColor));
            }
            ResetNomal();
            this.SetData();
        }
        public void LoadGridPrimitive(int count = 10, float r = .5f, Func<float, float, float> func = null, bool isContinuousNomal = true) {
            ClearBuffer();
            if (func == null)
                func = (x, y) => {
                    return 0;
                };
            var cellx = Vector3.Right * r;
            var celly = Vector3.Up * r;
            var gridcount = (count * count) - 3;
            var offset = r * count / 2;
            for (int i = 0; i < count; i++) {
                var xdir = cellx * i;
                for (int j = 0; j < count; j++) {
                    var ydir = xdir + celly * j;
                    var zposition = func.Invoke(xdir.X, ydir.Y);
                    var zdir = Vector3.Backward * zposition;
                    var transform = matrix.CreateTranslation(zdir);
                    var currentposition = Vector3.Transform(ydir, transform);
                    if (isContinuousNomal) {
                        var currentnomal = Vector3.TransformNormal(ydir, transform);
                        AddVertex(currentposition, currentnomal);
                    } else {
                        AddVertex(currentposition, Vector3.Zero);
                    }
                    var x3 = i * count + j;
                    var x4 = (i + 1) * count + j;
                    if (i < count - 1 && j < count - 1) {
                        AddIndex(x3 + 1);
                        AddIndex(x3);
                        AddIndex(x4);
                        //
                        AddIndex(x4);
                        AddIndex(x4 + 1);
                        AddIndex(x3 + 1);
                    }
                }
            }
            //自定义法线 验证，结果和上面直接使用效果无异议
            if (!isContinuousNomal) {
                ResetNomal();
            }
            SetData();
            this.World = matrix.CreateTranslation(-offset, -offset, 0);
        }

        private void ResetNomal() {
            var count = indices.Count - 3;
            for (int i = 0; i < count; i += 3) {
                var x1 = vertices[indices[i]].position - vertices[indices[i + 1]].position;
                var x2 = vertices[indices[i]].position - vertices[indices[i + 2]].position;
                var x3 = Vector3.Cross(x1, x2);
                vertices[indices[i]].SetNormal(x3);
                vertices[indices[i + 1]].SetNormal(x3);
                vertices[indices[i + 2]].SetNormal(x3);
            }
        }
        /// <summary>
        /// Constructs a new sphere primitive,
        /// with the specified size and tessellation level.
        /// </summary>
        public void LoadSpherePrimitive(float diameter = 1, int tessellation = 16, float dxzr = 1) {
            if (tessellation < 3)
                throw new ArgumentOutOfRangeException("tessellation");
            ClearBuffer();
            int verticalSegments = tessellation;
            int horizontalSegments = tessellation * 2;
            float radius = diameter / 2;
            // Start with a single vertex at the bottom of the sphere.
            AddVertex(Vector3.Down * radius, Vector3.Down);
            // Create rings of vertices at progressively higher latitudes.
            for (int i = 0; i < verticalSegments - 1; i++) {
                float latitude = ((i + 1) * MathHelper.Pi /
                                            verticalSegments) - MathHelper.PiOver2;
                float dy = (float)Math.Sin(latitude);
                float dxz = (float)Math.Cos(latitude) * dxzr;
                // Create a single ring of vertices at this latitude.
                for (int j = 0; j < horizontalSegments; j++) {
                    float longitude = j * MathHelper.TwoPi / horizontalSegments;
                    float dx = (float)Math.Cos(longitude) * dxz;
                    float dz = (float)Math.Sin(longitude) * dxz;
                    Vector3 normal = new Vector3(dx, dy, dz);
                    AddVertex(normal * radius, normal);
                }
            }
            // Finish with a single vertex at the top of the sphere.
            AddVertex(Vector3.Up * radius, Vector3.Up);
            // Create a fan connecting the bottom vertex to the bottom latitude ring.
            for (int i = 0; i < horizontalSegments; i++) {
                AddIndex(0);
                AddIndex(1 + (i + 1) % horizontalSegments);
                AddIndex(1 + i);
            }
            // Fill the sphere body with triangles joining each pair of latitude rings.
            for (int i = 0; i < verticalSegments - 2; i++) {
                for (int j = 0; j < horizontalSegments; j++) {
                    int nextI = i + 1;
                    int nextJ = (j + 1) % horizontalSegments;
                    AddIndex(1 + i * horizontalSegments + j);
                    AddIndex(1 + i * horizontalSegments + nextJ);
                    AddIndex(1 + nextI * horizontalSegments + j);
                    //
                    AddIndex(1 + i * horizontalSegments + nextJ);
                    AddIndex(1 + nextI * horizontalSegments + nextJ);
                    AddIndex(1 + nextI * horizontalSegments + j);
                }
            }
            // Create a fan connecting the top vertex to the top latitude ring.
            for (int i = 0; i < horizontalSegments; i++) {
                var currentVertex = vertices.Count;
                AddIndex(currentVertex - 1);
                AddIndex(currentVertex - 2 - (i + 1) % horizontalSegments);
                AddIndex(currentVertex - 2 - i);
            }
            SetData();
        }
        /// <summary>
        /// Constructs a new cylinder primitive,
        /// with the specified size and tessellation level.
        /// </summary>
        public void LoadCylinderPrimitive(float height = 1, float diameter = 1, int tessellation = 32) {
            if (tessellation < 3)
                throw new ArgumentOutOfRangeException("tessellation");
            ClearBuffer();
            height /= 2;
            float radius = diameter / 2;
            // Create a ring of triangles around the outside of the cylinder.
            for (int i = 0; i < tessellation; i++) {
                Vector3 normal = GetCircleVector(i, tessellation);
                AddVertex(normal * radius + Vector3.Up * height, normal);
                AddVertex(normal * radius + Vector3.Down * height, normal);
                AddIndex(i * 2);
                AddIndex(i * 2 + 1);
                AddIndex((i * 2 + 2) % (tessellation * 2));
                AddIndex(i * 2 + 1);
                AddIndex((i * 2 + 3) % (tessellation * 2));
                AddIndex((i * 2 + 2) % (tessellation * 2));
            }
            // Create flat triangle fan caps to seal the top and bottom.
            CreateCap(tessellation, height, radius, Vector3.Up);
            CreateCap(tessellation, height, radius, Vector3.Down);
            SetData();
        }
        /// <summary>
        /// Helper method creates a triangle fan to close the ends of the cylinder.
        /// </summary>
        void CreateCap(int tessellation, float height, float radius, Vector3 normal) {
            // Create cap indices.
            for (int i = 0; i < tessellation - 2; i++) {
                var currentVertex = vertices.Count;
                if (normal.Y > 0) {
                    AddIndex(currentVertex);
                    AddIndex(currentVertex + (i + 1) % tessellation);
                    AddIndex(currentVertex + (i + 2) % tessellation);
                } else {
                    AddIndex(currentVertex);
                    AddIndex(currentVertex + (i + 2) % tessellation);
                    AddIndex(currentVertex + (i + 1) % tessellation);
                }
            }
            // Create cap vertices.
            for (int i = 0; i < tessellation; i++) {
                Vector3 position = GetCircleVector(i, tessellation) * radius +
                                   normal * height;

                AddVertex(position, normal);
            }
        }
        /// <summary>
        /// Helper method computes a point on a circle.
        /// </summary>
        static Vector3 GetCircleVector(int i, int tessellation) {
            float angle = i * MathHelper.TwoPi / tessellation;
            float dx = (float)Math.Cos(angle);
            float dz = (float)Math.Sin(angle);
            return new Vector3(dx, 0, dz);
        }
        /// <summary>
        /// Constructs a new torus primitive,
        /// with the specified size and tessellation level.
        /// </summary>
        public void LoadTorusPrimitive(float diameter = 1, float thickness = 0.333f, int tessellation = 32) {
            if (tessellation < 3)
                throw new ArgumentOutOfRangeException("tessellation");
            ClearBuffer();
            // First we loop around the main ring of the torus.
            for (int i = 0; i < tessellation; i++) {
                float outerAngle = i * MathHelper.TwoPi / tessellation;
                // Create a transform matrix that will align geometry to
                // slice perpendicularly though the current ring position.
                Matrix transform = Matrix.CreateTranslation(diameter / 2, 0, 0) *
                                   Matrix.CreateRotationY(outerAngle);
                // Now we loop along the other axis, around the side of the tube.
                for (int j = 0; j < tessellation; j++) {
                    float innerAngle = j * MathHelper.TwoPi / tessellation;
                    float dx = (float)Math.Cos(innerAngle);
                    float dy = (float)Math.Sin(innerAngle);
                    // Create a vertex.
                    Vector3 normal = new Vector3(dx, dy, 0);
                    Vector3 position = normal * thickness / 2;
                    position = Vector3.Transform(position, transform);
                    normal = Vector3.TransformNormal(normal, transform);
                    AddVertex(position, normal);
                    // And create indices for two triangles.
                    int nextI = (i + 1) % tessellation;
                    int nextJ = (j + 1) % tessellation;
                    AddIndex(i * tessellation + j);
                    AddIndex(i * tessellation + nextJ);
                    AddIndex(nextI * tessellation + j);
                    AddIndex(i * tessellation + nextJ);
                    AddIndex(nextI * tessellation + nextJ);
                    AddIndex(nextI * tessellation + j);
                }
            }
            SetData();
        }
        /// <summary>
        /// Constructs a new cube primitive, with the specified size.
        /// </summary>
        public void LoadCubePrimitive(float size = 1) {
            ClearBuffer();
            // A cube has six faces, each one pointing in a different direction.
            Vector3[] normals =
            {
                new Vector3(0, 0, 1),
                new Vector3(0, 0, -1),
                new Vector3(1, 0, 0),
                new Vector3(-1, 0, 0),
                new Vector3(0, 1, 0),
                new Vector3(0, -1, 0),
            };

            // Create each face in turn.
            foreach (Vector3 normal in normals) {
                // Get two vectors perpendicular to the face normal and to each other.
                Vector3 side1 = new Vector3(normal.Y, normal.Z, normal.X);
                Vector3 side2 = Vector3.Cross(normal, side1);
                // Six indices (two triangles) per face.
                AddIndex(vertices.Count + 0);
                AddIndex(vertices.Count + 1);
                AddIndex(vertices.Count + 2);
                //
                AddIndex(vertices.Count + 0);
                AddIndex(vertices.Count + 2);
                AddIndex(vertices.Count + 3);
                // Four vertices per face.
                AddVertex((normal - side1 - side2) * size / 2, normal);
                AddVertex((normal - side1 + side2) * size / 2, normal);
                AddVertex((normal + side1 + side2) * size / 2, normal);
                AddVertex((normal + side1 - side2) * size / 2, normal);
            }
            SetData();
        }
        public void LoadCubePrimitive(float size = 1, float width = 1, float height = 1, float length = 1) {
            ClearBuffer();
            // A cube has six faces, each one pointing in a different direction.
            Vector3[] normals =
            {
                new Vector3(0, 0, length),
                new Vector3(0, 0, -length),
                new Vector3(width, 0, 0),
                new Vector3(-width, 0, 0),
                new Vector3(0, height, 0),
                new Vector3(0, -height, 0),
            };

            // Create each face in turn.
            foreach (Vector3 normal in normals) {
                // Get two vectors perpendicular to the face normal and to each other.
                Vector3 side1 = new Vector3(normal.Y, normal.Z, normal.X);
                Vector3 side2 = Vector3.Cross(normal, side1);
                // Six indices (two triangles) per face.
                AddIndex(vertices.Count + 0);
                AddIndex(vertices.Count + 1);
                AddIndex(vertices.Count + 2);
                //
                AddIndex(vertices.Count + 0);
                AddIndex(vertices.Count + 2);
                AddIndex(vertices.Count + 3);
                // Four vertices per face.
                AddVertex((normal - side1 - side2) * size / 2, normal);
                AddVertex((normal - side1 + side2) * size / 2, normal);
                AddVertex((normal + side1 + side2) * size / 2, normal);
                AddVertex((normal + side1 - side2) * size / 2, normal);
            }
            SetData();
        }
        #endregion
    }
    public struct VertexPositionNormalColor : IVertexType {
        public Vector3 position;
        public Vector3 normal;
        public Vector4 color;
        public VertexPositionNormalColor(Vector3 position, Vector3 normal, Vector4 color) {
            this.position = position; this.normal = normal; this.color = color;
        }
        public VertexPositionNormalColor SetNormal(Vector3 nomal) {
            this.normal = nomal;
            return this;
        }
        public VertexPositionNormalColor SetPosition(Vector3 pos) {
            this.position = pos;
            return this;
        }
        public VertexPositionNormalColor SetColor(Vector4 color) {
            this.color = color;
            return this;
        }
        public VertexPositionNormalColor Transform(matrix transform) {
            var p = Vector3.Transform(position, transform);
            var n = Vector3.TransformNormal(normal, transform);
            var c = this.color;
            return new VertexPositionNormalColor(p, n, c);
        }
        #region IVertexType 成员
        public VertexDeclaration VertexDeclaration {
            get {
                return new VertexDeclaration(
                    new VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Position, 0),
                    new VertexElement(12, VertexElementFormat.Vector3, VertexElementUsage.Normal, 0),
                    new VertexElement(24, VertexElementFormat.Vector4, VertexElementUsage.Color, 0)                    );
            }
        }
        #endregion
    }
}
